#ifndef THREADPOOL_H
#define THREADPOOL_H

#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <future>
#include <queue>
#include <vector>
#include <functional>


class ThreadPool {
public:
    static ThreadPool* getInstance(){
        static ThreadPool threaPool;
        return &threaPool;
    }

    ~ThreadPool(){
        _stop = true;
        _notEmpty.notify_all();
        for (std::thread& t:_threadVec) {
            t.join();
        }
    };

    template <typename Func, typename... Args>
    auto submitTask(Func&& func, Args&&... args) -> std::future<decltype(func(args...))>{
        using ReturnType = decltype(func(args...));
        auto taskptr = std::make_shared<std::packaged_task<ReturnType()>>(std::bind(std::forward<Func>(func), std::forward<Args>(args)...));
        std::future<ReturnType> result = taskptr -> get_future();

        std::unique_lock<std::mutex> ulock(_mtx);
        _notFull.wait(ulock, [this](){return _numTask < _maxTaskNum;});
        _taskQue.emplace([taskptr](){
            (*taskptr)();
        });
        ++_numTask;
        _notEmpty.notify_one();
        return result;
    }

    void setNumThread(int num) {
        _numThreads = num;
    }

    void start() {
        for (int i=0; i < _numThreads; ++i) {
            _threadVec.emplace_back(std::thread(&ThreadPool::consumeFunc, this));
        }
    }

private:
    std::mutex _mtx;
    std::condition_variable _notEmpty;
    std::condition_variable _notFull;

    int _numThreads;
    int _maxTaskNum;
    std::atomic_int _numTask;
    std::vector<std::thread> _threadVec;

    using Task = std::function<void()>;
    std::queue<Task> _taskQue;
    bool _stop;

    void consumeFunc() {
        while (true) {
            std::unique_lock<std::mutex> ulock(_mtx);
            _notEmpty.wait(ulock, [this]() {return _numTask!=0 || _stop;});
            if (_numTask == 0 && _stop) return;
            Task task = std::move(_taskQue.front());
            _taskQue.pop();
            --_numTask;
            _notFull.notify_one();
            ulock.unlock();
            task();
        }
    }

    ThreadPool(int numThreads=std::thread::hardware_concurrency() + 1,
                int maxTaskNum=1024)
        : _numThreads(numThreads)
        , _maxTaskNum(maxTaskNum)
        , _numTask(0)
        , _stop(false)
    {}

};



#endif